/*
 * Copyright (C) 2001-2011 Jacek Sieka, arnetheduck on gmail point com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifndef DCPLUSPLUS_DCPP_CLIENT_H
#define DCPLUSPLUS_DCPP_CLIENT_H

#include "compiler.h"

#include <atomic>

#include "forward.h"

#include "User.h"
#include "Speaker.h"
#include "BufferedSocketListener.h"
#include "ClientListener.h"
#include "DebugManager.h"
#include "SearchQueue.h"
#include "OnlineUser.h"

#include "BufferedSocket.h"

class ClientBase
{
	public:
	
		ClientBase() : type(DIRECT_CONNECT) { }
		
		enum P2PType { DIRECT_CONNECT, DHT };
		P2PType type;
		
		P2PType getType() const
		{
			return type;
		}
		
		virtual const string& getHubUrl() const = 0;
		virtual string getHubName() const = 0;
		virtual bool isOp() const = 0;
		virtual void connect(const OnlineUser& user, const string& token) = 0;
		virtual void privateMessage(const OnlineUserPtr& user, const string& aMessage, bool thirdPerson = false) = 0;
	
};

/** Yes, this should probably be called a Hub */
class Client : public ClientBase, public Speaker<ClientListener>, public BufferedSocketListener, protected TimerManagerListener
{
	public:
		typedef unordered_map<string*, Client*, noCaseStringHash, noCaseStringEq> List;
		typedef List::const_iterator Iter;
		
		virtual void connect();
		virtual void disconnect(bool graceless);
		
		virtual void connect(const OnlineUser& user, const string& token) = 0;
		virtual void hubMessage(const string& aMessage, bool thirdPerson = false) = 0;
		virtual void privateMessage(const OnlineUserPtr& user, const string& aMessage, bool thirdPerson = false) = 0; // !SMT!-S
		virtual void sendUserCmd(const UserCommand& command, const StringMap& params) = 0;
		
		uint64_t search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList, void* owner);
		void cancelSearch(void* aOwner)
		{
			searchQueue.cancelSearch(aOwner);
		}
		
		virtual void password(const string& pwd) = 0;
		virtual void info(bool force) = 0;
		
		virtual size_t getUserCount() const = 0;
		int64_t getAvailable() const
		{
			return availableBytes;
		};
		
		virtual void send(const AdcCommand& command) = 0;
		
		virtual string escape(string const& str) const
		{
			return str;
		}
		
		bool isConnected() const
		{
			return state != STATE_DISCONNECTED;
			// [!]IRainman to see other fixes, look:
			// void Client::connect()
			// void Client::on(Failed, const string& aLine)
			// void Client::disconnect(bool graceLess)
			// Lock lock(csSock); // [+] brain-ripper
			// return sock && sock->isConnected();
		}
		
		bool isReady() const
		{
			return state != STATE_CONNECTING && state != STATE_DISCONNECTED;
		}
		
		bool isSecure() const;
		bool isTrusted() const;
		std::string getCipherName() const;
		vector<uint8_t> getKeyprint() const;
		
		bool isOp() const
		{
			return getMyIdentity().isOp();
		}
		
		virtual void refreshUserList(bool) = 0;
		
		virtual void getUserList(OnlineUserList& list) const = 0;
		
		virtual OnlineUserPtr getUser(const UserPtr& aUser);
		virtual OnlineUserPtr findUser(const string& aNick) const = 0;
		
		uint16_t getPort() const
		{
			return port;
		}
		const string& getAddress() const
		{
			return address;
		}
		
		const string& getIp() const
		{
			return ip;
		}
		string getIpPort() const
		{
			return getIp() + ':' + Util::toString(port);
		}
		string getLocalIp() const;
		
		void updated(const OnlineUserPtr& aUser)
		{
			fire(ClientListener::UserUpdated(), this, aUser);    // !SMT!-fix
		}
		
		static int getTotalCounts()
		{
			return counts[COUNT_NORMAL] + counts[COUNT_REGISTERED] + counts[COUNT_OP];
		}
		
		static string getCounts()
		{
			char buf[128];
			return string(buf, snprintf(buf, _countof(buf), "%ld/%ld/%ld", counts[COUNT_NORMAL].load(), counts[COUNT_REGISTERED].load(), counts[COUNT_OP].load()));
		}
//[+]FlylinkDC
		string getCountsIndivid()
		{
			// [!] IRainman Exclusive hub, send H:1/0/0 or similar
			if (getMyIdentity().isOp())
			{
				return "0/0/1";
			}
			else if (getMyIdentity().isRegistered())
			{
				return "0/1/0";
			}
			return "1/0/0";
		}
		void getCountsIndividInt(long& p_normal, long& p_registered, long& p_op)
		{
			// [!] IRainman Exclusive hub, send H:1/0/0 or similar
			p_normal = p_registered = p_op = 0;
			if (getMyIdentity().isOp())
			{
				p_op = 1;
				return;
			}
			else if (getMyIdentity().isRegistered())
			{
				p_registered = 1;
				return;
			}
			p_normal = 1;
		}
//[~]FlylinkDC
		const string& getRawCommand(const int aRawCommand) const
		{
			switch (aRawCommand)
			{
				case 1:
					return rawOne;
				case 2:
					return rawTwo;
				case 3:
					return rawThree;
				case 4:
					return rawFour;
				case 5:
					return rawFive;
			}
			return Util::emptyString;
		}
		
		StringMap& escapeParams(StringMap& sm)
		{
			for (StringMapIter i = sm.begin(); i != sm.end(); ++i)
			{
				i->second = escape(i->second);
			}
			return sm;
		}
		
		void setSearchInterval(uint32_t aInterval)
		{
			// min interval is 10 seconds
			searchQueue.interval = max(aInterval + 2000, (uint32_t)(10 * 1000));
		}
		
		uint32_t getSearchInterval() const
		{
			return searchQueue.interval;
		}
		
		void cheatMessage(const string& msg)
		{
			fire(ClientListener::CheatMessage(), this, msg);
		}
		void reportUser(const Identity& i)
		{
			fire(ClientListener::UserReport(), this, i);
		}
		
		void reconnect();
		void shutdown();
		bool isActive() const;
		
		void send(const string& aMessage)
		{
			send(aMessage.c_str(), aMessage.length());
		}
		void send(const char* aMessage, size_t aLen);
		
		string getMyNick() const
		{
			return getMyIdentity().getNick();
		}
		string getHubName() const
		{
			return getHubIdentity().getNick().empty() ? getHubUrl() : getHubIdentity().getNick();
		}
		string getHubDescription() const
		{
			return getHubIdentity().getDescription();
		}
		
		Identity& getHubIdentity()
		{
			return hubIdentity;
		}
		
		const string& getHubUrl() const
		{
			return hubUrl;
		}
//[+]FlylinkDC
		bool WaitForInitialization(DWORD dwTimeout) const
		{
			return WaitForSingleObject(m_hEventClientInitialized, dwTimeout) == WAIT_OBJECT_0;
		}
//[~]FlylinkDC
		GETSET(Identity, myIdentity, MyIdentity);
		GETSET(Identity, hubIdentity, HubIdentity);
		
		GETSET(string, defpassword, Password);
		
		GETSET(string, currentNick, CurrentNick);
		GETSET(string, currentDescription, CurrentDescription);
		
		GETSET(string, rawOne, RawOne);
		GETSET(string, rawTwo, RawTwo);
		GETSET(string, rawThree, RawThree);
		GETSET(string, rawFour, RawFour);
		GETSET(string, rawFive, RawFive);
		GETSET(string, favIp, FavIp);
		GETSET(string, clientId, ClientId); // !SMT!-S
		
		GETSET(uint64_t, lastActivity, LastActivity);
		GETSET(uint32_t, reconnDelay, ReconnDelay);
		
	private:
		string  m_last_encoding;
	public:
		const string& getEncoding() const
		{
			return m_last_encoding;
		}
		void setEncoding(const string& p_encoding)
		{
			m_last_encoding = p_encoding;
		}
		
		GETSET(bool, registered, Registered);
		GETSET(bool, autoReconnect, AutoReconnect);
		GETSET(bool, stealth, Stealth);
//[+]FlylinkDC
		GETSET(string, currentEmail, CurrentEmail);
		
		GETSET(bool, hideShare, HideShare); // Hide Share Mod
#ifdef IRAINMAN_SET_USER_IP_ON_LOGON
		GETSET(bool, setUserIpOnLogon, SetUserIpOnLogon);
#endif
#ifdef IRAINMAN_ENABLE_AUTO_BAN
		virtual bool hubIsNotSupportSlot() = 0;// [+]IRainman
#endif // IRAINMAN_ENABLE_AUTO_BAN
//[~]FlylinkDC
	protected:
		friend class ClientManager;
		Client(const string& hubURL, char separator, bool secure_);
		virtual ~Client();
		
		enum CountType
		{
			COUNT_NORMAL,
			COUNT_REGISTERED,
			COUNT_OP,
			COUNT_UNCOUNTED,
		};
		
		static atomic<long> counts[COUNT_UNCOUNTED];
		
		enum States
		{
			STATE_CONNECTING,   ///< Waiting for socket to connect
			STATE_PROTOCOL,     ///< Protocol setup
			STATE_IDENTIFY,     ///< Nick setup
			STATE_VERIFY,       ///< Checking password
			STATE_NORMAL,       ///< Running
			STATE_DISCONNECTED  ///< Nothing in particular
		} state;
		
		SearchQueue searchQueue;
		BufferedSocket* sock;
		
		// [+] brain-ripper
		// initialization event, to delay processing of command line
		// until client is initialized
		HANDLE m_hEventClientInitialized;
		
		// [+] brain-ripper
		// need to protect socket:
		// shutdown may be called while calling
		// function that uses a sock && sock->... expression
		mutable CriticalSection csSock;
		
//-old  public:
		int64_t availableBytes;
//-old  protected:

		void updateCounts(bool aRemove);
		void updateActivity()
		{
			lastActivity = GET_TICK();
		}
		
		/** Reload details from favmanager or settings */
		void reloadSettings(bool updateNick);
		
		virtual string checkNick(const string& nick) = 0;
		virtual void search(int aSizeMode, int64_t aSize, int aFileType, const string& aString, const string& aToken, const StringList& aExtList) = 0;
		
		// TimerManagerListener
		virtual void on(Second, uint64_t aTick) noexcept;
		// BufferedSocketListener
		virtual void on(Connecting) noexcept
		{
			fire(ClientListener::Connecting(), this);
		}
		virtual void on(Connected) noexcept;
		virtual void on(Line, const string& aLine) noexcept;
		virtual void on(Failed, const string&) noexcept;
		
	private:
	
		Client(const Client&);
		Client& operator=(const Client&);
		
		string hubUrl;
		string address;
		string ip;
		string localIp;
		string keyprint;

		uint16_t port;
		char separator;
		bool secure;
		CountType countType;
};

#endif // !defined(CLIENT_H)

/**
 * @file
 * $Id: Client.h 568 2011-07-24 18:28:43Z bigmuscle $
 */
